<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<script>
    const template = `<div><p>Vue</p><p>Template</p></div>`

    const State = {
        initial: 1,
        tagOpen: 2,
        tagName: 3,
        text: 4,
        tagEnd: 5,
        tagEndName: 6
    }

    function isAlpha(char) {
        return char >= 'a' && char <= 'z' || char >= 'A' && char <= 'Z'
    }

    function tokenize(str) {
        let currentState = State.initial
        const chars = [] // 字符缓冲区
        const tokens = [] // tokens
        while (str) {
            const char = str[0] // 取出，没有消费
            switch (currentState) {
                case State.initial:
                    if (char === '<') {
                        currentState = State.tagOpen
                        str = str.slice(1) // 消费
                    } else if (isAlpha(char)) {
                        currentState = State.text
                        chars.push(char) // 推入缓冲区
                        str = str.slice(1) // 消费
                    }
                    break
                case State.tagOpen:
                    if (isAlpha(char)) {
                        currentState = State.tagName
                        chars.push(char)
                        str = str.slice(1)
                    } else if (char === '/') {
                        currentState = State.tagEnd
                        str = str.slice(1)
                    }
                    break
                case State.tagName:
                    if (isAlpha(char)) {
                        chars.push(char)
                        str = str.slice(1)
                    } else if (char === '>') {
                        currentState = State.initial
                        tokens.push({ // 推入token
                            type: 'tag',
                            name: chars.join('')
                        })
                        chars.length = 0 // 清空缓冲区
                        str = str.slice(1)
                    }
                    break
                case State.text:
                    if (isAlpha(char)) {
                        chars.push(char)
                        str = str.slice(1)
                    } else if (char === '<') {
                        currentState = State.tagOpen
                        tokens.push({
                            type: 'text',
                            content: chars.join('')
                        })
                        chars.length = 0
                        str = str.slice(1)
                    }
                    break
                case State.tagEnd:
                    if (isAlpha(char)) {
                        currentState = State.tagEndName
                        chars.push(char)
                        str = str.slice(1)
                    }
                    break
                case State.tagEndName:
                    if (isAlpha(char)) {
                        chars.push(char)
                        str = str.slice(1)
                    } else if (char === '>') {
                        currentState = State.initial
                        tokens.push({
                            type: 'tagEnd',
                            name: chars.join('')
                        })
                        chars.length = 0
                        str = str.slice(1)
                    }
                    break
            }
        }
        return tokens
    }

    function parse(str) {
        const tokens = tokenize(str)

        const root = {
            type: 'Root',
            children: []
        }
        const elementStack = [root] // 一开始只有root

        while (tokens.length) {
            const parent = elementStack[elementStack.length - 1] // 栈顶
            const t = tokens[0] // 每次都取出第一个token
            switch (t.type) {
                case 'tag':
                    const elementNode = {
                        type: 'Element',
                        tag: t.name,
                        children: []
                    }
                    parent.children.push(elementNode) // 父元素的children收集该子节点
                    elementStack.push(elementNode) // 栈顶入栈
                    break
                case 'text':
                    const textNode = {
                        type: 'Text',
                        content: t.content
                    }
                    parent.children.push(textNode) // 父元素的children收集该子节点
                    break
                case 'tagEnd':
                    elementStack.pop() // 栈顶出栈
                    break
            }
            tokens.shift() // 消费token
        }

        return root
    }

    const ast = parse(template)
    console.log(ast)
</script>
</body>
</html>
